package com.googlecode.cswish.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Service;

import com.googlecode.cswish.annotation.GenericType;
import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.annotation.ScopeValue;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.ParameterMap;
import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.struts.DynamicAction;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.conversion.PageElementService;
import com.googlecode.cswish.struts.conversion.StrutsFreemarkerManager;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.bean.ModelUtil;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;


/**
 * A util class to call service method
 * 
 * @author feng Date: 2009-7-9
 */
@Service
public class InvocationUtil {

	private static final Log logger = LogFactory.getLog(InvocationUtil.class);

	@Resource
	private BeanIntrospector beanIntrospector;

	@Resource
	private OgnlExUtil ognlExUtil;
	
	@Resource
	private Routing routing;
	
	@Resource
	private PageElementService pageElementService;
	
	@Resource
	private StrutsFreemarkerManager strutsFreemarkerManager;
	
	@Resource
	private ObjectFactory objectFactory;
	
	@Resource
	private GenericService genericService;
	
	@Resource
	private Functions functions;
	
	@Resource
	private CacheService cacheService;
	
	private ValueStackFactory valueStackFactory;

    @Inject
    public void setValueStackFactory(ValueStackFactory valueStackFactory) {
        this.valueStackFactory = valueStackFactory;
    }

    // 2种Key:
    // 1) 基于站点路径的cache
	// 2) service name + ',' + method name --> ServiceInfo
	private Map<String, ServiceInfo> serviceInfoCache;
	private Object NULL = new Object();
	
	public class ServiceInfo {
		public Class<?> serviceType;
		public Method serviceMethod;
		public Map<String, ClassType> parameteTypes;
		
		public boolean isResult() {
			return serviceMethod != null && Result.class.isAssignableFrom(serviceMethod.getReturnType());
		}
	}
	
	public class ClassType {
		public Class type;
		public String genericType;
		
		public ClassType(Class type, String genericType) {
			this.type = type;
			this.genericType = genericType;
		}
	}
	
	private ServiceInfo BLANK_SERVICE_INFO = new ServiceInfo();

	private Map<Class, Object> primitiveDefaults;
	
    public InvocationUtil() {
		Map<Class, Object> map = new HashMap<Class, Object>();
		map.put(Boolean.TYPE, Boolean.FALSE);
		map.put(Byte.TYPE, new Byte((byte) 0));
		map.put(Short.TYPE, new Short((short) 0));
		map.put(Character.TYPE, new Character((char) 0));
		map.put(Integer.TYPE, new Integer(0));
		map.put(Long.TYPE, new Long(0L));
		map.put(Float.TYPE, new Float(0.0f));
		map.put(Double.TYPE, new Double(0.0));
		map.put(BigInteger.class, new BigInteger("0"));
		map.put(BigDecimal.class, new BigDecimal(0.0));
		primitiveDefaults = Collections.unmodifiableMap(map);
	}
    
    @PostConstruct
    private void init() {
    	serviceInfoCache = cacheService.newCache(ServiceInfo.class);
    }
    
    class ModelInterceptor implements MethodInterceptor {
    	
		private Object service;
		private String actionName;
		
		public ModelInterceptor(Object service, String actionName) {
			this.service = service;
			this.actionName = actionName;
		}

		public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy)
				throws Throwable {
			ServiceInfo serviceInfo = getServiceInfo(service.getClass(), method);
			Map<String, Object> defaultParameters = getDefaultParameters(serviceInfo, actionName, new HashMap<String, Object>());
			Object result = null;
			try {
				Object[] defaultParams = defaultParameters.values().toArray(new Object[defaultParameters.size()]);
				for (int i = 0; i < args.length; i++) {
					if (args[i] == null) {
						args[i] = defaultParams[i];
					}
				}
				result = method.invoke(service, args);
			} catch (Exception ex) {
				logger.error("Fail to invoke service:" + service.getClass().getName() + '.' + method.getName(), ex);
			}
			return result;
		}
    }
    
    public <T> T wrap(T service, String actionName) {
    	ModelInterceptor modelInterceptor = new ModelInterceptor(service, actionName);
		T t = (T) Enhancer.create(ClassUtil.getClass(service), null, modelInterceptor);
		return t;
    }
    
    /**
     * Convert page parameter to service parameter
     *  
     * @param serviceInfo
     * @param parameters
     * @return
     */
    public Map<String, Object> populateParameter(final ServiceInfo serviceInfo, String actionName, Map<String, String[]> parameters) {
    	Map<String, Object> result = new HashMap<String, Object>(){
    		@Override
    	    public Object get(Object key) {
    	    	Object value = super.get(key);
    	    	return value == NULL ? null : value;
    	    }
    		
    	    @Override
    	    @SuppressWarnings({ "unchecked", "rawtypes" })
    		public Object put(String key, Object value) {
    	    	if (serviceInfo != null && serviceInfo.parameteTypes != null && value != null) {
    	    		ClassType parameterType = serviceInfo.parameteTypes.get(key);
    		    	if (parameterType != null && !parameterType.type.isAssignableFrom(value.getClass())) {
    		    		XWorkConverter converter = ognlExUtil.getXWorkConverter();
    					value = converter.convertValue(value, parameterType.type);
    				}
    	    	}
    	    	return super.put(key, value);
    	    }
    	};
    	Map<String, Object> defaultParameters = getDefaultParameters(serviceInfo, actionName, new HashMap<String, Object>());
    	result.putAll(defaultParameters);
    	
    	if (parameters != null) {
    		if (valueStackFactory == null) {
    			objectFactory.injectInternalBeans(this);
    		}
    		
    		ValueStack newStack = valueStackFactory.createValueStack();
            Map<String, Object> context = newStack.getContext();
            ReflectionContextState.setCreatingNullObjects(context, true);
    		newStack.getRoot().add(result);
    		
	    	for (Entry<String, String[]> parameter : parameters.entrySet()) {
	    		String name = parameter.getKey();
	    		String[] values = parameter.getValue();
	    		try {
	                newStack.setParameter(name, values);
	            } catch (RuntimeException e) {
	            	if (functions.isDebug()) {
	            		String message = "Unexpected Exception caught setting '" + name + "' on '" +
	            			serviceInfo.serviceType.getName() + '.' + serviceInfo.serviceMethod.getName()
	            			+ ": " + e.getMessage();
	            		logger.error(message);
	            	}
	            }
	    	}
    	}
    	return result;
    }
    
    /**
     * Call the specified url and get the executed html result
     * 
     * @param pathNoSite
     * @return
     */
    public String invokeUrl(String pathNoSite, Class<? extends DynamicAction> actionClass) {
    	try {
			URI uri = new URI(pathNoSite);
			String path = uri.getPath();
			
			DynamicAction serviceParameters = (DynamicAction) objectFactory.buildBean(actionClass, null);
			Invoker invoker = routing.path2Invoker(path, false);
			
			// see BaseActionSupportAction.prepareParameter
			serviceParameters.initialize(invoker.actionName, routing.invoker2PathNoExtension(invoker, true));
			serviceParameters.putAll(getDefaultParameters(serviceParameters.getServiceInfo(), null, serviceParameters));
			
			List<NameValuePair> params = URLEncodedUtils.parse(uri, "utf-8");
			for (NameValuePair param : params) {
				ModelUtil.setProperty(serviceParameters, param.getName(), param.getValue());
			}
			
			// prepare the result data
			Object result = invoke(invoker.actionName, serviceParameters);
			serviceParameters.put("execute", result);
			
			// execute template
			PageElements pageElements = pageElementService.getPageElements(invoker.actionName, invoker.methodName);
			String htmlPage = strutsFreemarkerManager.executeTpl('/' + ContextHolder.get().getSiteName() + path, true, pageElements, serviceParameters);
			return htmlPage;
    	} catch (Exception ex) {
    		logger.error("Failed to call path", ex);
    		return null;
    	}
    }
    
    /**
     * 
     * @param actionName
     * @param serviceParameters only the first level parameter, don't support the neste name
     * @return
     */
    public Object invoke(String actionName, Map<String, Object> serviceParameters) {
    	Invoker invoker = routing.action2Invoker(actionName);
		Object result = invoke(null, invoker.methodName, serviceParameters, actionName, false);
    	return result;
    }
    
    public Object invokeAndConvertParam(String serviceName, String methodName, List<Object> parameters, boolean enableProteced) {
		ServiceInfo serviceInfo = getServiceInfo(serviceName, methodName, enableProteced);

		Object result;
		if (serviceInfo != null) {
			String newActionName = beanIntrospector.getActionName(beanIntrospector.getSimpleName(serviceName) + '.' + methodName);
			Map<String, Object> defaultParameters = getDefaultParameters(serviceInfo, newActionName, new HashMap<String, Object>());
			Object[] params;
			if (!defaultParameters.isEmpty()) {
				int paramLen = defaultParameters.size();
				List<Object> convertedParameters = new ArrayList<Object>(paramLen);
				int index = 0;
				for (Entry<String, Object> entry : defaultParameters.entrySet()) {
					Object value = entry.getValue();
					Object paramValue = null;
					if (parameters != null && index < parameters.size()) {
						paramValue = parameters.get(index);
					}
					if (value != null && paramValue == null) {
						convertedParameters.add(value);
					} else {
						if (paramValue != null) {
							ClassType parameterType = serviceInfo.parameteTypes.get(entry.getKey());
		    		    	if (parameterType != null && !parameterType.type.isAssignableFrom(paramValue.getClass())) {
		    		    		paramValue = ognlExUtil.getXWorkConverter().convertValue(paramValue, parameterType.type);
		    				}
						}
						convertedParameters.add(paramValue);
					}
					index++;
				}
				params = convertedParameters.toArray();
			} else {
				params = null;
			}
			try {
				Object service = objectFactory.getBean(serviceInfo.serviceType.getName(), false);
				result = serviceInfo.serviceMethod.invoke(service, params);
			} catch (Exception ex) {
				logger.error("Fail to invoke service:" + serviceName + '#' + methodName, ex);
				result = null;
			}
		} else {
			logger.error("no matched method is found: " + serviceName + '#' + methodName);
			result = null;
		}
		return result;
    }
    
	/**
	 * Invoke a service object and prepare the Scope Parameter value
	 * 
	 * @param service
	 * @param methodName
	 * @param serviceParameters
	 * @return
	 */
	public <T> T invoke(Object service, String methodName, Map<String, Object> serviceParameters, String actionName, boolean enableProteced) {
		String serviceType;
		if (service == null) {
			serviceType = beanIntrospector.getServiceName(beanIntrospector.getSimpleName(actionName));
		} else {
			serviceType = ClassUtil.getClassName(service);
		}
		ServiceInfo serviceInfo = getServiceInfo(serviceType, methodName, enableProteced);

		T result = null;
		if (serviceInfo != null) {
			//Map valueStackContext = ActionContext.getContext().getActionInvocation().getStack().getContext();
			//String originalActionName = (String) valueStackContext.get(FrameConstant.STACK_ACTIONNAME);
			try {
				//int index = originalActionName.lastIndexOf('.');
				//String actionName = originalActionName.substring(0, index + 1) + StringUtils.capitalize(methodName) + "Action";				
				String newActionName = actionName == null ? beanIntrospector.getActionName(
						beanIntrospector.getSimpleName(serviceInfo.serviceType.getName()) + '.' + methodName) : actionName;
				Map<String, Object> defaultParameters = getDefaultParameters(serviceInfo, newActionName, new HashMap<String, Object>());
				if (serviceParameters != null) {
					for (Entry<String, Object> item : serviceParameters.entrySet()) {
						if (defaultParameters.containsKey(item.getKey())) {
							defaultParameters.put(item.getKey(), item.getValue());
						}
					}
				}
				
				Object[] params = null;
				try {
					params = defaultParameters.values().toArray(new Object[defaultParameters.size()]);
					for (Object value : params) {
						if (value instanceof ParameterMap) {
							((ParameterMap)value).setDisableAutoCreation(true);
						}
					}
					if (service == null) {
						service = objectFactory.getBean(serviceInfo.serviceType, false);
					}
					
					result = (T)serviceInfo.serviceMethod.invoke(service, params);
				} catch (Exception ex) {
					String sParam = StringUtils.join(params, ',');
					logger.error("Fail to invoke service:" + serviceInfo.serviceType.getName() + '.' + methodName + '(' + sParam + ')', ex);
				}
			} finally {
				//valueStackContext.put(FrameConstant.STACK_ACTIONNAME, originalActionName);
			}
		}
		return result;
	}

	private Method getServiceMethod(Class serviceClass, String methodName, boolean enableProtected) {
		Method serviceMethod = null;
		if (serviceClass != null) {
			for (Method method : serviceClass.getMethods()) {
				if (method.getAnnotation(Deprecated.class) == null && (
						enableProtected || method.getAnnotation(Protected.class) == null)) {
					if (method.getName().equals(methodName)) {
						serviceMethod = method;
						break;
					}
				}
			}
		}
		return serviceMethod;
	}
	
	public Object invoke(ServiceInfo serviceInfo, Map<String, Object> serviceParameters, boolean loadDefaultParameter) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Object result = null;
    	if (serviceInfo != null) {
    		Class<?> serviceType = serviceInfo.serviceType;
    		Method serviceMethod = serviceInfo.serviceMethod;
			Collection<String> paramNames = serviceInfo.parameteTypes.keySet();
			Object [] values = new Object[paramNames.size()];
			int i = 0;
			for (String paramName : paramNames) {
				Object value = serviceParameters.get(paramName);
				values[i++] = value;
				
				if (value instanceof ParameterMap) {
					((ParameterMap)value).setDisableAutoCreation(true);
				}
			}
	    
			if (logger.isDebugEnabled()) {
				logger.debug("Call Service: " + serviceType.getName() + '.' + serviceMethod.getName());
			}
			Object instance;
			if (beanIntrospector.isModel(serviceType.getName()) >= 0) {
				instance = serviceParameters.get("model");
			} else {
				instance = objectFactory.getBean(serviceType.getName(), false);
			}
			
			if (loadDefaultParameter) {
				String simpleName = beanIntrospector.getSimpleName(serviceInfo.serviceType.getName());
				String actionName = beanIntrospector.getActionName(simpleName, serviceInfo.serviceMethod.getName());
				//String newActionName = beanIntrospector.get
				Map<String, Object> defaultParameters = getDefaultParameters(serviceInfo, actionName, new HashMap<String, Object>());
				if (serviceParameters != null) {
					for (Entry<String, Object> item : serviceParameters.entrySet()) {
						if (defaultParameters.containsKey(item.getKey())) {
							defaultParameters.put(item.getKey(), item.getValue());
						}
					}
				}
				try {
					Object[] params = defaultParameters.values().toArray(new Object[defaultParameters.size()]);
					for (Object value : params) {
						if (value instanceof ParameterMap) {
							((ParameterMap)value).setDisableAutoCreation(true);
						}
					}
					result = serviceMethod.invoke(instance, params);
				} catch (Exception ex) {
					logger.error("Fail to invoke service:" + serviceInfo.serviceType.getName() + '.' + serviceMethod.getName(), ex);
				}
			} else {
				result = serviceMethod.invoke(instance, values);
			}
    	}
		return result;
	}
	
	public ServiceInfo getServiceInfo(String pathWithSite) {
		ServiceInfo serviceInfo = serviceInfoCache.get(pathWithSite);
		if (serviceInfo != null) {
			return serviceInfo == BLANK_SERVICE_INFO ? null : serviceInfo;
		}
		
		Invoker invoker = routing.path2Invoker(pathWithSite, true);
		serviceInfo = getServiceInfo(invoker, false, false);
		if (serviceInfo == null) {
			// try the inexact match
			String method = invoker.methodName;
			int i = 1, len = method.length();
			for (; i < len; i++) {
				char c = method.charAt(i);
				if (c < 'a' || c > 'z') {
					break;
				}
			}
			if (i < len) {
				String key = invoker.toString() + ',' + false;
				serviceInfoCache.remove(key);
				String inexactMethod = method.substring(0, i);
				invoker.methodName = inexactMethod;		// NOTICE: the key of 'getServiceInfo' is irrelevant to invoker.methdName
				serviceInfo = getServiceInfo(invoker, false, false);
			}
		}
		
		if (serviceInfo == null) {
			serviceInfoCache.put(pathWithSite, BLANK_SERVICE_INFO);
		} else {
			serviceInfoCache.put(pathWithSite, serviceInfo);
		}
		return serviceInfo;
	}
	
	public ServiceInfo getServiceInfo(Invoker invoker, boolean checkModelMethod, boolean enableProtected) {
		String key = invoker.toString() + ',' + checkModelMethod;
		ServiceInfo serviceInfo = serviceInfoCache.get(key);
		if (serviceInfo != null) {
			return serviceInfo == BLANK_SERVICE_INFO ? null : serviceInfo;
		}
		
		// 1. check method
		String methodName = invoker.methodName;
		if (methodName != null) {
			// 2. load class: action --> service --> generic
			// 2.1 try to load action
			serviceInfo = getServiceInfo(invoker.toString(), methodName, enableProtected);
			
			
			// 2.2 try to load service
			if (serviceInfo == null) {
				String simpleName = beanIntrospector.getSimpleName(invoker.toString());
				String serviceName = beanIntrospector.getServiceName(simpleName);
				serviceInfo = getServiceInfo(serviceName, methodName, enableProtected);
			}
			
			// 2.3 try to load model
			if (checkModelMethod && serviceInfo == null) {
				String simpleName = beanIntrospector.getSimpleName(invoker.toString());
				String modelName = beanIntrospector.getModelName(simpleName);
				serviceInfo = getServiceInfo(modelName, methodName, enableProtected);
			}
			
			// no matched service info
			if (serviceInfo == null) {
				if (logger.isDebugEnabled()) {
					logger.debug("No matched Service for request: " + invoker);
				}
				serviceInfoCache.put(key, BLANK_SERVICE_INFO);
			} else {
				serviceInfoCache.put(key, serviceInfo);
			}
		}
		
		return serviceInfo;
	}
	
	public ServiceInfo getServiceInfo(String serviceType, String methodName, boolean enableProtected) {
		if (serviceType == null || serviceType == null) {
			return null;
		}
		String key = serviceType + ',' + methodName;
		ServiceInfo serviceInfo = serviceInfoCache.get(key);
		if (serviceInfo != null) {
			return serviceInfo == BLANK_SERVICE_INFO ? null : serviceInfo;
		}

		Class type = ClassUtil.getClass(serviceType);
		Method serviceMethod = null;
		if (type != null) {
			serviceMethod = getServiceMethod(type, methodName, enableProtected);
		}
		if (serviceMethod == null && beanIntrospector.isService(serviceType) >= 0) {
			Class theObjectType = objectFactory.getBean(GenericService.class, false).getClass();
			if (Enhancer.isEnhanced(theObjectType)) {
				type = theObjectType.getSuperclass();
			} else {
				type = theObjectType.getClass();
			}
			serviceMethod = getServiceMethod(type, methodName, enableProtected);
		}
		return getServiceInfo(type, serviceMethod);
	}
	
	public ServiceInfo getServiceInfo(Class<?> serviceType, Method serviceMethod) {
		if (serviceMethod == null || serviceType == null) {
			return null;
		}
		
		String key = serviceType.getName() + ',' + serviceMethod.getName();
		ServiceInfo serviceInfo = serviceInfoCache.get(key);
		
		if (serviceInfo == null) {
			Map<String, ClassType> parameteTypes = Collections.EMPTY_MAP;
			if (serviceType != null && serviceMethod != null) {
				String [] paramNames = ClassEx.getProtectedParameterNames(serviceMethod);
				String[] genericTypes = ClassEx.getGenericTypes(serviceMethod);
				String[] annoGenericTypes = ClassEx.getAnnotationGenericTypes(serviceMethod);
				parameteTypes = new LinkedHashMap<String, ClassType>(paramNames.length * 4 / 3);
				Class<?>[] parameterTypes = serviceMethod.getParameterTypes();
				int i = 0;
				for (String paramName : paramNames) {
					Class<?> parameterType = parameterTypes[i];
					String genericType = parameterType.getName();
					if (annoGenericTypes != null && annoGenericTypes[i] != null) {
						genericType = annoGenericTypes[i];
					} else if (genericTypes[i] != null) {
						genericType = genericTypes[i];
					}
					
					if (paramName == null) {
						paramName = "param" + (i + 1);
					}
					parameteTypes.put(paramName, new ClassType(parameterType, genericType));
					i++;
				}
			}
			
			if (serviceType != null && serviceMethod != null) {
				serviceInfo = new ServiceInfo();
				serviceInfo.serviceType = serviceType;
				serviceInfo.serviceMethod = serviceMethod;
				serviceInfo.parameteTypes = parameteTypes;
				serviceInfoCache.put(key, serviceInfo);
			} else {
				serviceInfoCache.put(key, BLANK_SERVICE_INFO);
				
			}
		}
		return serviceInfo == BLANK_SERVICE_INFO ? null : serviceInfo;
	}
	
	public Map<String, Object> getDefaultParameters(ServiceInfo serviceInfo, String actionName, Map<String, Object> context) {
		Map<String, Object> parameters = new LinkedHashMap<String, Object>();
    	if (serviceInfo != null) {
    		Method serviceMethod = serviceInfo.serviceMethod;
    		Annotation[][] annotations = serviceMethod.getParameterAnnotations();
    		
    		ActionContext actionContext = ActionContext.getContext();
    		if (actionContext != null) {
	    		Map valueStackContext = actionContext.getValueStack().getContext();
	    		String defaultActionName = (String) valueStackContext.get(FrameConstant.STACK_ACTIONNAME);
	    		try {
	    			if (actionName != null) {
	    				valueStackContext.put(FrameConstant.STACK_ACTIONNAME, actionName);
	    			}
		    		int i = 0;
		    		Set<Map.Entry<String, ClassType>> entries = serviceInfo.parameteTypes.entrySet();
		    		for (Map.Entry<String, ClassType> item : entries) {
		    			Object value = getServiceParameterValue(actionContext, context, serviceMethod, item.getValue(), item.getKey(), annotations[i]);
		    			if (value == null) {
		    				value = getInitValueByParameterType(item.getKey(), item.getValue().type, item.getValue().genericType);
		    			}
		    			parameters.put(item.getKey(), value);
		    			i++;
		    		}
	    		} finally {
	    			valueStackContext.put(FrameConstant.STACK_ACTIONNAME, defaultActionName);
	    		}
    		} else {
    			Set<Map.Entry<String, ClassType>> entries = serviceInfo.parameteTypes.entrySet();
	    		for (Map.Entry<String, ClassType> item : entries) {
	    			Object value = getInitValueByParameterType(item.getKey(), item.getValue().type, item.getValue().genericType);
	    			parameters.put(item.getKey(), value);
	    		}
    		}
    	}
    	return parameters;
	}
	
	public Object getInitValueByParameterType(String parameterName, Class parameterType, String genericType) {
		Object value = null;
		if (parameterType.isPrimitive()) {
			value = primitiveDefaults.get(parameterType);
		} else if (!parameterType.equals(String.class)) {
			if (Collection.class.isAssignableFrom(parameterType)) {
				value = new EventArrayList();
    		} else if (parameterType == Map.class) {
    			value = new HashMap();
    		} else if (parameterType.isArray()) {
    			int length = 0;		// FIXME: pass from the fontend or set in the backend?
    			value = Array.newInstance(parameterType.getComponentType(), length);
    		} else {
				String beanName = parameterName;
				if (parameterType.equals(Object.class) || GenericType.PARAM_MODEL.equals(genericType)) {
					beanName = GenericType.PARAM_MODEL; // ParameterMap
					value = objectFactory.buildBean(beanName, parameterType, null);
				} else if (!parameterType.getName().startsWith("java.")) {	// if (beanIntrospector.isModel(parameterType.getName()) >= 0)
					value = objectFactory.buildBean(beanName, parameterType, null);
				}
    		}
		}
		return value;
	}
	
	private Object getServiceParameterValue(ActionContext actionContext, Map<String, Object> context, Method serviceMethod, ClassType parameterType, String property, Annotation[] annotattions) {
    	String methodName = serviceMethod.getName();
    	ActionInvocation invocation = actionContext.getActionInvocation();
    	if (invocation != null) {
	    	String actionName = (String) invocation.getStack().getContext().get("actionName");
			String key = actionName + '@' + property;			// use action name instead of service name
			Object paramScope = getActionParamScope(annotattions, parameterType.type);
	
			if (paramScope != null && paramScope != NULL) {
				String scopeKey = getScopeKey(actionContext, key, actionName, methodName, property,
						(ParamScope) paramScope);
				return findValueFromParamScope(actionContext, scopeKey, actionName, methodName, property, parameterType,
						(ParamScope) paramScope, context);
			}
    	}
		return null;
	}
    
    /**
	 * Find the annotation definition
	 * 
	 * @param method
	 * @return
	 */
	private ParamScope getActionParamScope(Annotation[] annotattions, Class parameterType) {
		ParamScope paramScope = null;
		for (Annotation annotattion : annotattions) {
			if (annotattion instanceof ParamScope) {
				paramScope = (ParamScope) annotattion;
				break;
			}
		}
		if (paramScope == null) {
			paramScope = (ParamScope) parameterType.getAnnotation(ParamScope.class);
		}

		return paramScope;
	}

	private String getScopeKey(ActionContext context, String defaultKey, String actionName, String methodName,
			String parameterName, ParamScope paramScope) {
		ValueStack stack = context.getValueStack();
		String key;
		if (StringUtils.isEmpty(paramScope.scopeKey())) {
			key = defaultKey;
		} else {
			try {
				ReflectionContextState.setDenyMethodExecution(context.getContextMap(), false);
				Map<String, String> propInfo = new HashMap<String, String>(5);
				//Invoker invoker = new Invoker(className);
				propInfo.put("className", beanIntrospector.getSimpleName(actionName));
				propInfo.put("methodName", methodName);
				propInfo.put("propertyName", parameterName);
				stack.getContext().put("propInfo", propInfo);
				key = stack.findString(paramScope.scopeKey());
			} finally {
				ReflectionContextState.setDenyMethodExecution(context.getContextMap(), true);
			}
		}
		return key;
	}

	/**
	 * Call ParamScope to find the parameter value from the life-cycle manager
	 * 
	 * @param serviceClass
	 * @param methodName
	 * @param parameterName
	 * @return
	 */
	private Object findValueFromParamScope(ActionContext context, String scopeKey, String className, String methodName,
			String parameterName, ClassType parameterType, ParamScope paramScope, Map<String, Object> action) {
		Object oldValue = null;
		// first, find the old value
		switch (paramScope.scope()) {
		case SESSION:
			oldValue = context.getSession().get(scopeKey);
			break;
		case REQUEST:
			oldValue = context.getValueStack().findValue(scopeKey);
			break;
		case APPLICATION:
			oldValue = context.getApplication().get(scopeKey);
			break;
		case ALL:
			break;
		}

		// second, try to initialize it according to the OGNL expression
		if (oldValue == null && paramScope.value().length() > 0) {
			try {
				ReflectionContextState.setDenyMethodExecution(context.getContextMap(), false);
				oldValue = context.getValueStack().findValue(paramScope.value());
			} finally {
				ReflectionContextState.setDenyMethodExecution(context.getContextMap(), true);
			}
		}
		
		// update current parameter value to relevant scope
		switch (paramScope.scope()) {
		case SESSION:
			if (oldValue == null) {
				oldValue = getInitValueByParameterType(parameterName, parameterType.type, parameterType.genericType);
			}
			context.getSession().put(scopeKey, oldValue);
			break;
		case REQUEST:
			action.put(parameterName, oldValue);
			break;
		case APPLICATION:
			if (oldValue == null) {
				oldValue = getInitValueByParameterType(parameterName, parameterType.type, parameterType.genericType);
			}
			context.getApplication().put(scopeKey, oldValue);
			break;
		case ALL:
			break;
		}

		if (oldValue != null && paramScope.scope() != ScopeValue.REQUEST) {
			// Find the action object and update the value
			action.put(parameterName, oldValue);
		}
		return oldValue;
	}
	
	
	public void forceLoadModelAndResults(Map<String, Object> parameters) {
		Object model = parameters.get("model");
		if (model != null) {
			try {
				Integer id = (Integer) ModelUtil.getSimpleProperty(model, "id");
				if (id != null) {
					Object dbmodel = parameters.get("dbmodel");
					if (dbmodel == null || !isSame(model, dbmodel)) {
						dbmodel = genericService.load(ClassUtil.getClass(model), id);
					}
					parameters.put("dbmodel", dbmodel);
				}
			} catch(Exception ex) {
				logger.debug("Failed to load the model info", ex);
			}
		}
		
		Object results = parameters.get("results");
		if (results != null && results instanceof Collection) {
			List dbResults = (List) parameters.get("dbresults");
			if (dbResults == null || !isSameCollection((Collection)results, dbResults)) {
				Collection coll = (Collection) results;
				dbResults = new ArrayList(coll.size());
				for (Object result : coll) {
					Object dbresult = null;
					if (result != null) {
						try {
							Integer id = (Integer) ModelUtil.getSimpleProperty(result, "id");
							if (id != null) {
								dbresult = genericService.load(ClassUtil.getClass(result), id);
							}
						} catch(Exception ex) {
							logger.debug("Failed to load the model info", ex);
						}
					}
					dbResults.add(dbresult);
				}
				parameters.put("dbresults", dbResults);
			}
		}
	}

	// TODO: remove the check? basically, the result is not same
	private boolean isSameCollection(Collection results, List dbResults) {
		int count = 0;
		for (Object result : results) {
			if (result != null) {
				count++;
			}
		}
		
		boolean equals = (count == dbResults.size());
		if (equals) {
			int index = 0;
			for (Object result : results) {
				if (result != null) {
					equals = isSame(result, dbResults.get(index));
					index++;
					if (!equals) {
						break;
					}
				}
			}
		}
		return equals;
	}

	// TODO: remove the check? basically, the result is not same
	private boolean isSame(Object model, Object dbmodel) {
		String className = ClassUtil.getClassName(model);
		String dbClassName = ClassUtil.getClassName(model);
		boolean equals = false;
		if (className.equals(dbClassName)) {
			Integer id = (Integer) ModelUtil.getSimpleProperty(model, "id");
			Integer dbid = (Integer) ModelUtil.getSimpleProperty(dbmodel, "id");
			
			equals = (id != null && id.equals(dbid));
		}
		return equals;
	}
	
	public void resetInvocation(String simpleModelName) {
		List<String> keys = new ArrayList<>(serviceInfoCache.keySet());
		String pathKeyPrefix = ContextHolder.get().getSiteName() + '/' + simpleModelName.replace('.', '/');
		String actionClassKeyPrefix = FrameConstant.PACKAGE_PREFIX + simpleModelName;
		String serviceClassKeyPrefix = beanIntrospector.getServiceName(simpleModelName);
		for (String key : keys) {
			if (key.startsWith(pathKeyPrefix) || key.startsWith(actionClassKeyPrefix)
					|| key.startsWith(serviceClassKeyPrefix)) {
				serviceInfoCache.remove(key);
			}
		}
	}
}